package com.wnxy.sddfp.gateway.filter;


import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.wnxy.sddfp.common.model.result.Result;
import com.wnxy.sddfp.common.model.result.enums.IErrorCode;
import com.wnxy.sddfp.common.model.result.enums.impl.BusinessCode;
import com.wnxy.sddfp.gateway.config.IgnorePathConfig;
import com.wnxy.sddfp.gateway.config.JwtTemplate;
import lombok.SneakyThrows;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.time.Duration;
import java.util.List;

/**
 * 网关全局过滤器，实现统一鉴权：拦截所有的请求
 */
@Configuration
public class AuthFilter implements GlobalFilter {
    public final JwtTemplate jwtTemplate;
    public final StringRedisTemplate stringRedisTemplate;
    public final IgnorePathConfig ignorePathConfig;


    public AuthFilter(JwtTemplate jwtTemplate, StringRedisTemplate stringRedisTemplate, IgnorePathConfig ignorePathConfig) {
        this.jwtTemplate = jwtTemplate;
        this.stringRedisTemplate = stringRedisTemplate;
        this.ignorePathConfig = ignorePathConfig;
    }

    public static final String AUTHORIZATION = "Authorization";
    public static final String BEARER = "Bearer ";

    @SneakyThrows
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 1、获取请求路径：放行一些资源
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();

        String path = request.getURI().getPath();
        System.out.println("path = " + path);
        AntPathMatcher pathMatcher = new AntPathMatcher();
        List<String> whiteList = ignorePathConfig.getWhiteList();

        boolean flag = whiteList.stream().anyMatch(urlPatter -> pathMatcher.match(urlPatter, path));
        if (flag) {
            // 放行，不进行token校验
            return chain.filter(exchange);
        }

        // 2、需要先获取请求头，去掉token前缀“Bearer ”， 得到token
        String authToken = request.getHeaders().getFirst(AUTHORIZATION);
        System.out.println("authToken = " + authToken);

        if (StringUtils.isEmpty(authToken)) {
            return responseError(response, BusinessCode.TOKEN_IS_NULL);
        }
        authToken = authToken.replace(BEARER, "");
        // 3、校验token
        if (!jwtTemplate.verify(authToken)) {
            return responseError(response, BusinessCode.TOKEN_IS_INVALID);
        }

        // 4、解析获取token数据： userId, 校验token是否过期
        Number userId = (Number) jwtTemplate.parseToken(authToken, "userId");
        String key = "userToken" + userId.toString();
        if (!stringRedisTemplate.hasKey(key)) {
            return responseError(response, BusinessCode.TOKEN_IS_EXPIRED);
        }


        // 5、token 续期
        stringRedisTemplate.expire(key, Duration.ofMinutes(30));

        // 6、网关中重写请求头，让请求头携带userId，再转发到微服务，这样在微服务中就可以直接获取登录用户id
        request.mutate().header("userId", userId.toString());

        // 7、微服务直接从请求头获取用户id
        return chain.filter(exchange);
    }

    private static Mono<Void> responseError(ServerHttpResponse response, IErrorCode resultCode) throws JsonProcessingException {
        // 返回错误
        Result<Object> result = Result.fail(resultCode);
        byte[] bytes = new ObjectMapper().writeValueAsBytes(result);
        DataBuffer dataBuffer = response.bufferFactory().wrap(bytes);
        return response.writeWith(Mono.just(dataBuffer));
    }

    public static void main(String[] args) {
        AntPathMatcher pathMatcher = new AntPathMatcher();
        // 参数1：匹配模式; /* 只匹配一层目录； /** 匹配多层目录
        // 参数2：要匹配的路径，这个路径去匹配参数1的格式
        boolean match = pathMatcher.match("/**/user", "/aa/bb/cc/user");
        System.out.println("match = " + match);
    }
}